# -*-coding:utf-8 -*-
from inspect import signature
import inspect

from win32com.client import Dispatch, constants
from PfclsAPI import *
import pywintypes
import re
import json
from glob import glob
from bs4 import BeautifulSoup as bs
import sys
from enum import Enum
import traceback

from pfcls_instant import *
# from pfcls import *

path = 'C:/Program Files/PTC/Creo 6.0.4.0/Common Files/vbapi/vbapidoc/api/'


class Ipfc(object):
    def __init__(self, data):
        self.parent = data['parent']
        self.child = data['child']
        self.property = data['properties']
        self.method = data['method']
        self.type = data['type']
        names = self.__dict__
        for key in self.method.keys():
            names[key] = self.method[key]

    def test(self):
        pass


def read_frame():
    global path
    with open(path + 'entities-frame.html') as ef:
        entities_frame = ef.read()
        table_pattern = re.compile(r'<table.*?</table>', re.S)
        temp = table_pattern.findall(entities_frame)
        dic = {}
        th_pattern = re.compile(r'.*<th.*?>(.*?)</th>.*', re.S)
        aa_pattern = re.compile(r'.*?<a.*?>(.*?)</a>.*?', re.S)
        for item in temp:
            match = th_pattern.match(item)
            if match:
                dic[match.group(1)] = aa_pattern.findall(item)
        return dic


def creat_instant():
    dic = read_frame()
    instants = {}
    # for item in dic['Classes']:
    #     instants[item] = {}
    #     instants[item]['type'] = 'Classes'
    # for item in dic['Collections']:
    #     instants[item] = {}
    #     instants[item]['type'] = 'Collections'
    for key in dic.keys():
        for item in dic[key]:
            instants[item] = {}
            instants[item]['type'] = key
    with open('pfcls_instant.json', 'w') as json_file:
        json_str = json.dumps(instants, indent=4)
        json_file.write(json_str)
    json_file.close()


def parse_html(name):
    global path
    parent = []
    child = []
    properties = []
    method = []
    filenames = glob(path + '*' + name[4:] + '.html')
    for filename in filenames:
        with open(filename, 'r') as f:
            files = f.read()
            name_pattern = re.compile(r'.*<h2>.*?;(.*?)</h2>.*', re.S)
            list = name_pattern.findall(files)
            for item in list:
                if item == name:
                    dl_pattren = re.compile(r'.*?<dl>(.*?)</dl>.*?', re.S)
                    dl = dl_pattren.findall(files)
                    for t in dl:
                        if re.match(r'.*<b>Direct Parent Classes:</b>.*', t, re.S):
                            parent = re.findall(r'.*?<a.*?>(.*?)</a>.*?', t, re.S)
                        if re.match(r'.*<b>Direct Known Subclasses:</b>.*', t, re.S):
                            child = re.findall(r'.*?<a.*?>(.*?)</a>.*?', t, re.S)
                    # pattern = re.compile(r'.*?<tt><a class.*?>(.*?)</a>.*?<tt>as <a class.*?>(.*?)</a>.*?', re.S)
                    # match = re.match(r'.*?<b>Property Summary</b>.*?(.*?)</table>.*?', files, re.S)
                    # if match:
                    #     properties = pattern.findall(match.group(1))
                    #     print(properties)
                    # match = re.match(r'.*?<b>Method Summary</b>.*?(.*?)</table>.*?', files, re.S)
                    # if match:
                    #     method = pattern.findall(match.group(1))
                    tr = re.findall(r'.*?<tr>(.*?)</tr>.*?', files, re.S)
                    for k in tr:
                        pn = re.match(r'.*?<tt><a class=.*?>(.*?)</a>.*?', k, re.S)  # 属性或方法名称
                        if pn:
                            pn = pn.group(1)
                            if re.match(r'.*?<tt>Property</tt>.*?', k, re.S):
                                pv1 = re.match(r'.*?<tt>as <a class=.*?>(.*?)</a>.*?', k, re.S)  # 属性类型或方法返回类型，ipfc对象
                                pv2 = re.match(r'.*?<tt>as (.*?) .*?</tt>.*?', k, re.S)  # 属性类型或方法返回类型，非ipfc对象
                                pv3 = re.match(r'.*?<tt>as (.*?)</tt>.*?', k, re.S)  # 属性类型或方法返回类型，非ipfc对象
                                for p in [pv1, pv2, pv3]:
                                    if p:
                                        pv = p.group(1)
                                        break
                                    else:
                                        pv = None
                                properties.append(('Property', pn, pv))
                                # print(properties)
                            if re.match(r'.*?<tt>Function</tt>.*?', k, re.S):
                                pv1 = re.match(r'.*?\(\).*?as <a class.*?>(.*?)</a>.*?', k, re.S)
                                pv2 = re.match(r'.*?<tt>.*?\(\).*?as (.*?) .*?</tt>.*?', k, re.S)
                                pv3 = re.match(r'.*?<tt>.*?\(\).*?as (.*?)</tt>.*?', k, re.S)
                                pv4 = re.match(r'.*?\(.*?\).*?as <a class.*?>(.*?)</a>.*?', k, re.S)
                                pv5 = re.match(r'.*?<tt>.*?\(.*?\).*?as (.*?) .*?</tt>.*?', k, re.S)
                                pv6 = re.match(r'.*?<tt>.*?\(.*?\).*?as (.*?)</tt>.*?', k, re.S)
                                for p in [pv1, pv2, pv3, pv4, pv5, pv6]:
                                    if p:
                                        pv = p.group(1)
                                        break
                                    else:
                                        pv = None
                                method.append(('Function', pn, pv))
                                # print(method)
                            if re.match(r'.*?<tt>Sub</tt>.*?', k, re.S):
                                method.append(('Sub', pn))
                                # print(method)

            f.close()
    return (parent, child, properties, method)


def parse_html_bybs(name):
    global path
    parent = {}
    child = {}
    properties = {}
    method = {}
    filenames = glob(path + '*' + name[4:] + '.html')
    for filename in filenames:
        soup = bs(open(filename), 'html.parser')
        h2 = soup.h2.string.split(chr(160))[-1]  # ASCII 32和160两种不同的空格，160为&nbsp;生成
        if h2 == name:
            print('filename:' + name)
            # 获取父类和子类
            dl = soup.findAll('dl')
            for item in dl:
                try:
                    bp = item.findAll('b', text='Direct Parent Classes:')
                    if bp != []:
                        pc = item.findAll('a', class_='class')
                        [parent.update({t.string: ''}) for t in pc]
                        # for t in pc:
                        #     parent[t.string] = ''
                    bc = item.findAll('b', text='Direct Known Subclasses:')
                    if bc != []:
                        cc = item.findAll('a', class_='enumtype')
                        [child.update({t2.string: ''}) for t2 in cc]
                        # for t2 in cc:
                        #     child[t2.string] = ''
                except Exception as e:
                    print(e)

            # 获取属性和方法
            try:
                for table in soup.findAll('table', class_='method-summary'):
                    tr = table.findAll('tr')
                    for index, t in enumerate(tr):
                        tt = t.findAll('tt')
                        # 获取属性
                        if tt != [] and tt[0].text == 'Property':
                            tmp = []
                            for k in tt:
                                tt_a = k.findAll('a')
                                tmp.append(k.text) if len(tt_a) == 0 else [tmp.append(a.text) for a in tt_a]
                            tmp[2] = re.sub(chr(160), ' ', tmp[2])  # 替换返回值中的&nbsp;
                            # 获取属性说明
                            info = tr[index + 1].find('td', class_="entities-text")
                            info = info.text if info else None
                            properties[tmp[1]] = {'return': tmp[2], 'info': info}
                        # 获取方法
                        if tt != [] and tt[0].text in ['Function', 'Sub']:
                            # 匹配方法名
                            fn_p = re.compile(r'.*?<a class=.*?>(.*?)</a>.*\(.*?', re.S)
                            # 匹配方法形参
                            fa_p = re.compile(r'.*?\((.*?)\).*?', re.S)
                            # 匹配方法返回值
                            fr_p = re.compile(r'.*?\).*?<a class=.*?>(.*?)</a>.*?|.*?\)(.*?)</tt>.*?', re.S)
                            fn = fn_p.match(str(t)).group(1)
                            fa = fa_p.match(str(t))
                            fa = re.sub(r'<a.*?>|</a>', '', fa.group(1)) if fa else None
                            fa = re.sub(chr(160), ' ', fa)  # &nbsp;替换为空格
                            fr_0 = fr_p.match(str(t))
                            fr = fr_0.group(1) if fr_0.group(1) else fr_0.group(2)
                            fr = re.sub(chr(10), '', fr)
                            info = tr[index + 1].find('td', class_="entities-text").text
                            method[fn] = {'type': tt[0].text, 'args': fa, 'return': fr, 'info': re.sub(r'\n', '', info)}



            except Exception as e:
                print('=============异常信息=============')
                print(name)
                print(traceback.format_exc())
                print('=============异常信息=============')
    return (parent, child, properties, method)


def write():
    with open('pfcls_instant.json', 'r') as json_file:
        instants = json.load(json_file)
        json_file.close()
        print(len(instants.keys()))
    index = 1
    for key in instants.keys():
        if instants[key]['type'] in ['Classes', 'Exceptions', 'Collections']:
            para = parse_html_bybs(key)
            instants[key]['parent'] = para[0]
            instants[key]['child'] = para[1]
            instants[key]['properties'] = para[2]
            instants[key]['method'] = para[3]
            index += 1
        # sys.exit() if index > 100 else None

    with open('pfcls_instant.json', 'w') as json_file:
        json_str = json.dumps(instants, indent=4)
        json_file.write(json_str)
        json_file.close()
        print(len(instants.keys()))


def get_comname():
    with open('middle_creatable.txt', 'r') as mid_file:
        mids = mid_file.readlines()
        mid_file.close()
    creatable_names = {}
    for item in mids:
        tmp = item.split('--->')
        creatable_names[tmp[0]] = re.sub(r'\n', '', tmp[1])
    return creatable_names


def add_enumvalue():
    # 添加枚举类成员数值和创建函数
    creatable_names = get_comname()

    with open('pfcls_instant.json', 'r') as json_file:
        instants = json.load(json_file)
        json_file.close()
    for key in instants.keys():
        if instants[key]['type'] == 'Enumerated Types':
            index = 'CC' + key[1:]
            try:
                disp = Dispatch(creatable_names[index])
                value = {}
                for key2 in disp._prop_map_get_.keys():
                    value[key2] = getattr(disp, key2)
                instants[key]['value'] = value
                instants[key]['com'] = creatable_names[index]
            except Exception as e:
                print('=============异常信息=============')
                print(key, '-------', creatable_names[index])
                print(traceback.format_exc())
                print('=============异常信息=============')

    with open('pfcls_instant.json', 'w') as json_file:
        json_str = json.dumps(instants, indent=4)
        json_file.write(json_str)
        json_file.close()


def add_methodcom():
    creatable_names = get_comname()

    with open('pfcls_instant.json', 'r') as json_file:
        instants = json.load(json_file)
        json_file.close()
    for key in instants.keys():
        if instants[key]['type'] in ['Classes', 'Exceptions']:
            method = instants[key]['method']
            for mk in method.keys():
                try:
                    if re.match(r'^CCpfc.*?\..*?', mk):
                        comname = creatable_names[mk.split('.')[0]]
                        instants[key]['method'][mk]['com'] = comname
                except Exception as e:
                    print('=============异常信息=============')
                    print(key, '-------', mk)
                    print(traceback.format_exc())
                    print('=============异常信息=============')

    with open('pfcls_instant.json', 'w') as json_file:
        json_str = json.dumps(instants, indent=4)
        json_file.write(json_str)
        json_file.close()


def check_mc():
    # 检查middle_creatable.txt
    creatable_name = get_comname()
    cont = 0
    with open('pfcls_instant.json', 'r') as f:
        files = f.read()
        f.close()
    for item in creatable_name.values():
        if len(item.split('.')) < 2:
            continue
        if re.match(r'.*?"com": "' + item + '".*?', files, re.S):
            cont += 1
        else:
            disp = Dispatch(item)
            name = type(disp).__name__
            print('{:<50}{:<50}'.format(item, name))
    print(cont)


def add_collentioncom():
    comnames = get_comname()
    with open('pfcls_instant.json', 'r') as json_file:
        instants = json.load(json_file)
        json_file.close()
    cont = 0
    for item in comnames.values():
        if len(item.split('.')) < 2:
            continue
        try:
            disp = Dispatch(item)
            class_name = type(disp).__name__
            if class_name in instants.keys():
                instants[class_name]['com'] = item
                cont += 1
        except Exception as e:
            print('=============异常信息=============')
            print(item)
            print(traceback.format_exc())
            print('=============异常信息=============')

    print(cont)
    with open('pfcls_instant.json', 'w') as json_file:
        json_str = json.dumps(instants, indent=4)
        json_file.write(json_str)
        json_file.close()


def add_CMpfccom():
    comnames = get_comname()
    with open('pfcls_instant.json', 'r') as json_file:
        instants = json.load(json_file)
        json_file.close()
    cont = 0
    for item in comnames.values():
        if len(item.split('.')) < 2:
            continue
        try:
            disp = Dispatch(item)
            class_name = type(disp).__name__
            class_name = 'C' + class_name[1:]
            if class_name in instants.keys():
                instants[class_name]['com'] = item
                cont += 1
        except Exception as e:
            print('=============异常信息=============')
            print(item)
            print(traceback.format_exc())
            print('=============异常信息=============')

    print(cont)
    with open('pfcls_instant.json', 'w') as json_file:
        json_str = json.dumps(instants, indent=4)
        json_file.write(json_str)
        json_file.close()


class DottableDict(dict):
    def __init__(self, *args, **kwargs):
        dict.__init__(self, *args, **kwargs)
        self.__dict__ = self

    def allowDotting(self, state=True):
        self.__dict__ = self if state else dict()


c = 1


def formatdict(dic, f):
    global c
    for key in dic.keys():
        if type(dic[key]) is dict:
            c += 1
            f.write('\t' * c)
            s = "'{}': {{\n".format(key)
            f.write(s)
            formatdict(dic[key], f)
            f.write('\t' * (c + 1))
            f.write('},\n')
        else:
            f.write('\t' * (c + 1))
            detail = re.sub(r'^ |\n|\t| $|' + chr(160), '', str(dic[key]))
            detail = re.sub(r' +', ' ', detail)
            if "'" in str(dic[key]) and '"' in str(dic[key]):
                f.write("""'{}': '''{}''',\n""".format(key, detail))
            elif "'" in str(dic[key]):
                f.write(''''{}': "{}",\n'''.format(key, detail))
            elif type(dic[key]) is int:
                f.write("'{}': {},\n".format(key, detail))
            else:
                f.write("'{}': '{}',\n".format(key, detail))
    c -= 1


def gen_instanspy():
    global c
    with open('pfcls_instant.json', 'r') as json_file:
        instants = json.load(json_file)
        json_file.close()

    with open('pfcls_instant.py', 'w', encoding='utf-8') as f:
        f.write('# -*- coding:utf-8 -*-\n')
        # f.write('from enum import Enum\n')
        # f.write('instants = {\n')
        typeflag = []

        for key in instants.keys():
            typename = instants[key]['type'].split(' ')[0]
            class_head = 'class {}(object):\n'.format(typename)
            if not typename in typeflag:
                typeflag.append(typename)
                f.write(class_head)
            c = 2
            f.write('\t{} = {{\n'.format(key))
            formatdict(instants[key], f)
            f.write('\t}\n')
        # f.write('}')
        f.close()


clist = []


def write_tpm(f, instants, name):
    global c
    f.write("\ttype = '{}'\n".format(instants[name]['type']))
    if instants[name]['properties'] != {}:
        f.write("\tproperties = {\n")
        c = 2
        formatdict(instants[name]['properties'], f)
        f.write('\t}\n')
    else:
        f.write('\tproperties = None\n')
    if instants[name]['method'] != {}:
        f.write("\tmethod = {\n")
        c = 2
        formatdict(instants[name]['method'], f)
        f.write('\t}\n')
    else:
        f.write('\tmethod = None\n')
    if 'com' in instants[name].keys():
        f.write("\tcom = '{}'\n".format(instants[name]['com']))
    f.write('\n' * 2)


def write_classes(instants, name, f):
    global clist, c
    if instants[name]['parent'] == {} and not name in clist:
        clist.append(name)
        f.write('class {}(object):\n'.format(name))
        # f.write('''\tdef __getattr__(self, attr):\n\t\treturn None\n''')
        write_tpm(f, instants, name)

        # f.write('\tchild = [')
        # child_list = ', '.join(list(instants[name]['child'].keys()))
        # f.write(child_list + ']\n')
    else:
        for item in instants[name]['parent'].keys():
            if not item in clist:
                write_classes(instants, item, f)
        if not name in clist:
            clist.append(name)
            f.write('class {}('.format(name))
            pl = inherit_sort(instants, list(instants[name]['parent'].keys()))
            for item in pl:
                f.write(item + ', ')
            f.write('object):\n')
            # f.write('''\tdef __getattr__(self, attr):\n\t\treturn None\n''')

            write_tpm(f, instants, name)

            # f.write('\tchild = [')
            # child_list = ', '.join(list(instants[name]['child'].keys()))
            # f.write(child_list + ']\n')


# 生成pfcls.py
def gen_pfclspy():
    with open('pfcls_instant.json', 'r') as json_file:
        instants = json.load(json_file)
        json_file.close()

    with open('pfcls.py', 'w', encoding='utf-8') as f:
        f.write('# -*- coding:utf-8 -*-\n')
        f.write('from win32com.client import Dispatch\n')
        f.write('from enum import Enum\n\n\n')
        lists = get_com(instants)
        create_comclass(lists, f)

        for key in instants.keys():
            itype = instants[key]['type']
            if itype == 'Enumerated Types':
                f.write('class {}(Enum):\n'.format(key))
                ivalue = instants[key]['value']
                for item in ivalue.keys():
                    f.write('\tEpfc{} = {}\n'.format(item, ivalue[item]))
                f.write("\tcom = '{}'\n".format(instants[key]['com']))
                f.write('\n' * 2)
            # if itype == "Classes":
            #     f.write('class {}: pass\n'.format(key))
            #创建信息类，可从pfcls_instant.json中找到
            # else:
            #     write_classes(instants, key, f)
            #     for item in instants[key]['parent'].keys():
            #         write_classes(instants, item, f)
        # for key in instants.keys():
        #     itype = instants[key]['type']
        #     if itype == "Classes":
        #         child_list = ', '.join(list(instants[key]['child'].keys()))
        #         f.write('{}.child = [{}]\n'.format(key, child_list))
    f.close()


# 将父类按继承关系排序，子类在父类左侧
def inherit_sort(instants, source):
    for i in range(len(source) - 1):
        for j in range(len(source) - i - 1):
            if source[i] in instants[source[i + j + 1]]['parent'].keys():
                source[i], source[i + j + 1] = source[i + j + 1], source[i]

    return source


# 获取com名称 Enumerated除外
def get_com(instants):
    ccpfclist = []
    for key in instants.keys():
        if instants[key]['type'] != 'Enumerated Types':
            methods = instants[key]['method'].keys()
            for item in methods:
                ccpfcname = item.split('.')
                if len(ccpfcname) == 2:
                    ccpfccom = instants[key]['method'][item]['com']
                    if not ccpfccom in ccpfclist:
                        ccpfclist.append(ccpfccom)
            if 'com' in instants[key].keys():
                ccpfclist.append(instants[key]['com'])
    return ccpfclist


# 生成class创建代码，输出到pfcls.py
def create_comclass(ccpfclist, f):
    for item in ccpfclist:
        disp = Dispatch(item)
        classname = type(disp).__name__
        f.write('class {}(object):\n'.format(classname))
        f.write("\tcom = '{}'\n".format(item))
        f.write('''\tdisp = Dispatch(com)\n''')
        for i in dir(disp):
            if not i.startswith('_') and not i in ['CLSID', 'coclass_clsid'] and not inspect.ismethod(eval('disp.' + i)):
                f.write("\t{0} = disp.{0}\n".format(i))
        f.write('\n')
        for i in dir(disp):
            if not i.startswith('_') and not i in ['CLSID', 'coclass_clsid'] and inspect.ismethod(eval('disp.' + i)):
                args = re.match(r'\((.*?)\)', str(signature(eval('disp.' + i)))).group(1)
                if args != '':
                    args = re.sub(r'=<PyOle.*?>', '', args)
                    args = re.sub(r'_| ', '', args).split(',')
                    args1 = '=None, '.join(args)
                    args2 = ', '.join(args)
                    f.write('''\t@classmethod\n\tdef {0}(cls, {1}=None):\n\t\treturn cls.disp.{0}({2})\n\n'''.format(i, args1, args2))
                else:
                    f.write('''\t@classmethod\n\tdef {0}(cls):\n\t\treturn cls.disp.{0}()\n\n'''.format(i))

        f.write('\n')

def gen_pfclsapi():
    with open('pfcls_instant.json', 'r') as json_file:
        instants = json.load(json_file)
        json_file.close()

    with open('PfclsAPI.py', 'w', encoding='utf-8') as f:
        f.write('# -*- coding:utf-8 -*-\n')
        f.write('from win32com.client import Dispatch\n\n\n')
        f.write('class classes:\n')
        l = get_com(instants)
        for item in l:
            name = type(Dispatch(item)).__name__
            name = re.sub(r'^I', 'C', name)
            value = "Dispatch('{}')".format(item)
            f.write('\t{} = {}\n'.format(name, value))

        f.write('\n\nclass enumerated:\n')
        for key in instants.keys():
            itype = instants[key]['type']
            if itype == 'Enumerated Types':
                ivalue = instants[key]['value']
                for item in ivalue.keys():
                    f.write('\tEpfc{} = {}\n'.format(item, ivalue[item]))
        f.close()
if __name__ == '__main__':

    print('start')
    gen_pfclsapi()
    
    # connect = pfcAsyncConnection.Start('C:/Program Files/PTC/Creo 6.0.4.0/Parametric/bin/parametric.exe')
    # session = connect.Session
    # print(session.ConnectionId)
    # connect.End()
    # session = pfcSession.GetCurrentSession()
    # print(session.ConnectionId)
    # m = pfcXSections.disp.Count
    # print(inspect.ismethod(m))
    # print(m)
    # s = signature(m)
    # # print(s)
    # l = re.match(r'\((.*?)\)', str(s)).group(1)
    #
    # print(l == '')
    # # for i in l:
    #     print(re.sub(r'<PyOle.*?>', 'None', i[1:]))
    #     # print(i)
    # check_mc()

    # creat_instant()
    # write()
    # add_enumvalue()
    # add_methodcom()
    # add_collentioncom()
    # add_CMpfccom()
    # gen_instanspy()
    gen_pfclspy()
